/*
 * @Author: Lin Hongwei
 * @Date: 2022-09-14 21:17:08
 * @LastEditTime: 2022-10-04 13:46:14
 * @LastEditors: Lin Hongwei
 * @email: 1365755396@qq.com
 * @CSDN: https://blog.csdn.net/weixin_45429409
 * @Description: 异步操作，添加上加载中和错误的标识
 */
import { useCallback, useReducer, useState } from "react";
import { useMountedStateRef } from "../hooks";

//状态的类型
interface State<D> {
  error: Error | null;
  data: D | null;
  status: "default" | "loading" | "error" | "success";
}

//初始化状态
const defaultState: State<null> = {
  status: "default",
  data: null,
  error: null,
};

//每回dispatch之前检查是否是挂载，非挂载状态下不进行提交
const useMountedDispatch = <T>(dispatch: (...args: T[]) => void) => {
  //监听组件挂载的状态，避免卸载后对组件添加数据
  const mountedStateRef = useMountedStateRef();
  return useCallback(
    (...args: T[]) => (mountedStateRef.current ? dispatch(...args) : void 0),
    [mountedStateRef, dispatch]
  );
};
/**
 * @description: 异步操作hook，异步的状态，操作完记录data
 * @param {*} D- 数据范型
 * @return {*} 提供给外界的数据信息
 */
export const useAsync = <D>(initState?: State<D>) => {
  // const [status, setStatus] = useState<State<D>>({
  //   ...defaultState,
  //   ...initState,
  // });
  //这里action可以自定义格式，无需跟传统的type字段一致
  const [status, dispatch] = useReducer(
    (state: State<D>, action: Partial<State<D>>) => ({ ...state, ...action }),
    {
      ...defaultState,
      ...initState,
    }
  );
  //对dispatch进行修饰，让只有在mounted的时候才进行提交
  const setDispatch = useMountedDispatch(dispatch);

  //添加输入
  //用useCallback优化
  const setData = useCallback(
    (data: D) =>
      // setStatus({
      setDispatch({
        data,
        status: "success",
        error: null,
      }),
    [setDispatch]
  );
  //发生错误
  //用useCallback优化
  const setError = useCallback(
    (error: Error) =>
      // setStatus({
      setDispatch({
        error,
        data: null,
        status: "error",
      }),
    [setDispatch]
  );
  //加载中,这里加载中的数据不变，所以data和error一致
  //用useCallback优化
  const setLoading = useCallback(
    () =>
      // 这里是用了status，可以使用函数的方式来消除
      // setStatus({
      //   ...status,
      //   status: "loading",
      // }),
      // setStatus(
      //   (preState) => ({
      //   ...preState,
      //   status: "loading",
      // })),
      setDispatch({
        status: "loading",
      }),
    [setDispatch]
  );

  //记录请求函数，供其他组件调用
  //注意useState使用函数初始化的时候是一个函数，该函数需要返回state
  const [retry, setRetry] = useState(() => () => {});

  //监听组件挂载的状态，避免卸载后对组件添加数据
  // const mountedStateRef = useMountedStateRef();

  //使用useCallback优化其他地方使用了对象而没有添加到依赖引起的eslint报错提示（添加又造成死循环）
  const run = useCallback(
    (promise: Promise<D>, runConfig?: { retry: () => Promise<D> }) => {
      if (!promise || !promise.then) {
        throw new Error("请传入Promise类型的数据");
      }
      if (runConfig?.retry) {
        //保存请求的函数
        setRetry(() => () => {
          //这里不使用promise是因为promise是已经执行完返回的（所以无法再次触发请求），这里不能拿到请求的函数，所以改用接受一个函数作为传入
          // run(promise)
          run(runConfig?.retry(), runConfig);
        });
      }

      setLoading();
      return promise
        .then((data) => {
          // if (mountedStateRef.current) setData(data);
          setData(data);
          return data;
        })
        .catch((err) => {
          // catch会消化异常，如果不主动抛出，外面是接收不到异常的
          setError(err);
          // return err;
          return Promise.reject(err);
        });
    },
    [setData, setError, setLoading]
  );
  return {
    isDefault: status.status == "default",
    isLoading: status.status == "loading",
    isError: status.status == "error",
    isSuccess: status.status == "success",
    run,
    setData,
    setError,
    setLoading,
    retry,
    ...status,
  };
};
